package org.abbett.eskine.dao.jpa;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Convenience mechanism to get a default toString(), hashCode, etc.
 * 
 * AbstractDAO is designed to only work with subclasses of this class. That can be changed, if needed.
 * 
 * Not sure how valuable this superclass is. Delete if it gets in the way.
 * 
 * @param <PK>
 *            Primary Key Type
 */
public abstract class JPAPersistable<PK> implements Serializable {

    protected String toString(Class<?> o, Object... keyValuePairs){
        final StringBuilder sb = new StringBuilder();
        sb.append(o.getSimpleName());
        sb.append("(");
        if(keyValuePairs.length % 2 == 0){
            for(int i=0;i < keyValuePairs.length; i=i+2){
                sb.append(keyValuePairs[i]);
                sb.append("=");
                sb.append(keyValuePairs[i+1]);
                sb.append(", ");
            }
        }
        sb.append(")");
        return sb.toString();
    }
    
    @Override
    public String toString() {
        return printMe(this);
    }

    @Override
    public boolean equals(Object obj) {
        return compareTwoBeans(this, obj);
    }

    @Override
    public int hashCode() {
        return generateHashCode(this);
    }

    // SB Quickly pasted from ISGBeanUTils to introduce fix.

    /**
     * Uses reflection to print contents of bean to String for debugging purposes.
     * 
     * @param o
     *            instance of object to be printed.
     */
    public static String printMe(Object o) {
        // Gather data via reflection, save to map
        Map<String, String> data = callEveryBeanMethodOnObject(o);
        // print data.
        final StringBuilder sb = new StringBuilder();
        sb.append(o.getClass().getSimpleName()); // append class name
        if (data.size() == 1) { // Foo(bar)
            sb.append("(");
            for (String val : data.values()) {
                sb.append(val);
            }
            sb.append(")");
        } else { // Foo{id=bar, name=fubar}
            sb.append("{");
            for (String methodName : data.keySet()) {
                sb.append("\n\t" + methodName + " = " + data.get(methodName));
            }
            sb.append("\n}");
        }
        return sb.toString();
    }

    /**
     * Call every method on object that begins with "get" or "is"
     * 
     * Generates unique signature
     * 
     * @return map of parameter to value.
     */
    public static Map<String, String> callEveryBeanMethodOnObject(Object o) {
        Map<String, String> data = new LinkedHashMap<String, String>();
        for (Method m : o.getClass().getMethods()) {
            final String methodName = m.getName();
            if ((methodName.startsWith("get") || methodName.startsWith("is")) && m.getParameterTypes().length == 0
                    && methodName != "getClass") {

                if (m.getReturnType().toString().contains("org.chip")) {
                    // no need to print me as I may be an entity bean capable of throwing Lazy initialization exception.
                    continue;
                }
                // TODO comment this line (SB)
                try {
                    Object value = m.invoke(o, (Object[]) null);
                    if (value instanceof byte[]) { // byte arrays are "special"
                        System.err.println("\n\nI was called\n\n");
                        data.put(methodName, "byte[" + ((byte[]) value).length + "]"); // cast to String
                        continue; // Skip for now. Is this causing gnome-terminal display issues?
                    }
                    data.put(methodName, "" + value); // cast to String

                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return data;
    }

    public static boolean compareTwoBeans(Object o1, Object o2) {
        if (o1 == o2) {
            return true;
        }
        if (o1 == null || o2 == null) { // from statement above, we know both aren't null.
            return false;
        }
        for (Method method0 : o1.getClass().getMethods()) {
            final String methodName = method0.getName();
            // no need to print org.chip classes as they may be an entity bean capable of throwing Lazy initialization
            // exception.
            if (method0.getReturnType().toString().contains("org.chip")) {
                // no need to print me as I may be an entity bean capable of throwing Lazy initialization exception.
                continue;
            }
            if ((methodName.startsWith("get") || methodName.startsWith("is"))
                    && method0.getParameterTypes().length == 0 && methodName != "getClass") {
                try {
                    Method method1 = o2.getClass().getMethod(methodName, (Class[]) null);
                    Object value1 = method0.invoke(o1, (Object[]) null);
                    Object value2 = method1.invoke(o2, (Object[]) null);
                    if (value1 == null) {
                        return value1 == value2; // both are null and therefore equal.
                    }
                    // handle byte arrays[]
                    if (method0.getReturnType() == byte[].class) {
                        return Arrays.equals((byte[]) value1, (byte[]) value2);
                    }

                    if (!value1.equals(value2)) {
                        return false;
                    }
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    throw new RuntimeException(e);
                }
            }

        }
        return true;
    }

    public static int generateHashCode(Object o) {
        return printMe(o).hashCode(); // TODO: is this reckless?
    }

    private static final long serialVersionUID = 1L;
}// clickframes::version=4154094859::clickframes